""" Win32 API wrappers.

Wraps common Win32 API's and constants using ctypes. Python semantics are applied.
"""


import ctypes
from ctypes.wintypes import HANDLE, HWND, BOOL, HGLOBAL
import weakref
import array


# ------------------------------------------------------------------------
# Constants for "waitable" functions
# ntstatus.h
STATUS_WAIT_0                   = 0x00000000L
STATUS_ABANDONED_WAIT_0         = 0x00000080L


# WinBase.h
WAIT_OBJECT_0                   = ((STATUS_WAIT_0 ) + 0 )
WAIT_IO_COMPLETION              = 0x000000C0L
WAIT_ABANDONED_0                = ((STATUS_ABANDONED_WAIT_0 ) + 0 )
WAIT_FAILED                     = 0xFFFFFFFF
INFINITE                        = 0xFFFFFFFF  # Infinite timeout

# WinError.h
WAIT_TIMEOUT                    = 258L


# ------------------------------------------------------------------------
def WaitForSingleObject(handle, milliseconds = 0):
    """Returns True if signaled, False if timeout occurred."""

    ret = ctypes.windll.kernel32.WaitForSingleObject(handle, milliseconds)
    if ret == WAIT_OBJECT_0:
        return True
    elif ret == WAIT_TIMEOUT:
        return False
    elif ret == WAIT_FAILED:
        raise ctypes.WinError()
    else:
        raise RuntimeError("WaitForSingleObject: Unknown return value from wait:", ret)


# ------------------------------------------------------------------------
def WaitForMultipleObjects(handles, waitAll, milliseconds):
    HandleArray = ctypes.c_void_p * len(handles)
    handles = HandleArray(*handles)
    ret = ctypes.WaitForMultipleObjects(len(handles), handles, bool(waitAll), milliseconds)


# ------------------------------------------------------------------------
# Constants for change notification API
# WinNT.h
FILE_NOTIFY_CHANGE_FILE_NAME    = 0x00000001
FILE_NOTIFY_CHANGE_DIR_NAME     = 0x00000002
FILE_NOTIFY_CHANGE_ATTRIBUTES   = 0x00000004
FILE_NOTIFY_CHANGE_SIZE         = 0x00000008
FILE_NOTIFY_CHANGE_LAST_WRITE   = 0x00000010
FILE_NOTIFY_CHANGE_LAST_ACCESS  = 0x00000020
FILE_NOTIFY_CHANGE_CREATION     = 0x00000040
FILE_NOTIFY_CHANGE_SECURITY     = 0x00000100


# ------------------------------------------------------------------------
def FindFirstChangeNotification(pathName, watchSubTree, notifyFilter):
    """The FindFirstChangeNotification function creates a change notification handle and
    sets up initial change notification filter conditions. A wait on a notification handle
    succeeds when a change matching the filter conditions occurs in the specified directory
    or subtree. The function does not report changes to the specified directory itself.
    """
    FindFirstChangeNotificationW = ctypes.windll.kernel32.FindFirstChangeNotificationW
    FindFirstChangeNotificationW.restype = HANDLE
    return FindFirstChangeNotificationW(unicode(pathName), bool(watchSubTree), notifyFilter)


# ------------------------------------------------------------------------
def FindNextChangeNotification(handle):
    if not ctypes.windll.kernel32.FindNextChangeNotification(handle):
        raise ctypes.WinError()


# ------------------------------------------------------------------------
def FindCloseChangeNotification(handle):
    if not ctypes.windll.kernel32.FindCloseChangeNotification(handle):
        raise ctypes.WinError()


# ------------------------------------------------------------------------
# Folder and version API's
MAX_PATH = 260

CSIDL_DESKTOP = 0x0000
CSIDL_INTERNET = 0x0001
CSIDL_PROGRAMS = 0x0002
CSIDL_CONTROLS = 0x0003
CSIDL_PRINTERS = 0x0004
CSIDL_PERSONAL = 0x0005
CSIDL_FAVORITES = 0x0006
CSIDL_STARTUP = 0x0007
CSIDL_RECENT = 0x0008
CSIDL_SENDTO = 0x0009
CSIDL_BITBUCKET = 0x000A
CSIDL_STARTMENU = 0x000B
CSIDL_MYDOCUMENTS = 0x000C
CSIDL_MYMUSIC = 0x000D
CSIDL_MYVIDEO = 0x000E
CSIDL_DIRECTORY = 0x0010
CSIDL_DRIVES = 0x0011
CSIDL_NETWORK = 0x0012
CSIDL_NETHOOD = 0x0013
CSIDL_FONTS = 0x0014
CSIDL_TEMPLATES = 0x0015
CSIDL_COMMON_STARTMENU = 0x016
CSIDL_COMMON_PROGRAMS = 0x0017
CSIDL_COMMON_STARTUP = 0x0018
CSIDL_COMMON_DESKTOPDIRECTORY = 0x0019
CSIDL_APPDATA = 0x001A
CSIDL_PRINTHOOD = 0x001B
CSIDL_LOCAL_APPDATA = 0x001C
CSIDL_ALTSTARTUP = 0x001D
CSIDL_COMMON_ALTSTARTUP = 0x001E
CSIDL_COMMON_FAVORITES = 0x001F
CSIDL_INTERNET_CACHE = 0x0020
CSIDL_COOKIES = 0x0021
CSIDL_HISTORY = 0x0022
CSIDL_COMMON_APPDATA = 0x0023
CSIDL_WINDOWS = 0x0024
CSIDL_SYSTEM = 0x0025
CSIDL_PROGRAM_FILES = 0x0026
CSIDL_MYPICTURES = 0x0027
CSIDL_PROFILE = 0x0028
CSIDL_SYSTEMX86 = 0x0029
CSIDL_PROGRAM_FILESX86 = 0x002A
CSIDL_PROGRAM_FILES_COMMON = 0x002B
CSIDL_PROGRAM_FILES_COMMONX86 = 0x002C
CSIDL_COMMON_TEMPLATES = 0x002D
CSIDL_COMMON_DOCUMENTS = 0x002E
CSIDL_COMMON_ADMINTOOLS = 0x002F
CSIDL_ADMINTOOLS = 0x0030
CSIDL_CONNECTIONS = 0x0031
CSIDL_COMMON_MUSIC = 0x0035
CSIDL_COMMON_PICTURES = 0x0036
CSIDL_COMMON_VIDEO = 0x0037
CSIDL_RESOURCES = 0x0038
CSIDL_RESOURCES_LOCALIZED = 0x0039
CSIDL_COMMON_OEM_LINKS = 0x003A
CSIDL_CDBURN_AREA = 0x003B
CSIDL_COMPUTERSNEARME = 0x003D
CSIDL_FLAG_PER_USER_INIT = 0x0800
CSIDL_FLAG_NO_ALIAS = 0x1000
CSIDL_FLAG_DONT_VERIFY = 0x4000
CSIDL_FLAG_CREATE = 0x8000
CSIDL_FLAG_MASK = 0xFF00


S_OK = 0
FACILITY_WIN32     = 7
def HRESULT_FACILITY(hr): return (hr >> 16) & 0x1fff

def SHGetFolderPath(csidl):
    path = ctypes.create_unicode_buffer(MAX_PATH)
    ret = ctypes.windll.shell32.SHGetFolderPathW(HWND(), csidl, HANDLE(), 0, path)
    if ret != S_OK:
        assert HRESULT_FACILITY(ret) == FACILITY_WIN32
        raise ctypes.WinError(ret) # Assume it's a win32 error code
    return path.value


def GetFileVersionInfo(filename, info):
    filename = unicode(filename)
    size = ctypes.windll.version.GetFileVersionInfoSizeW(filename, None)
    if not size:
        raise ctypes.WinError()

    res = ctypes.create_string_buffer(size)

    # Load file informations into buffer res
    ctypes.windll.version.GetFileVersionInfoW(filename, None, size, res)

    # Look for codepages
    r = ctypes.c_uint()
    l = ctypes.c_uint()
    ctypes.windll.version.VerQueryValueW(res, u'\\VarFileInfo\\Translation', ctypes.byref(r), ctypes.byref(l))

    # If no codepage -> empty string
    if not l.value:
        return ""

    # Take the first codepage (what else ?)
    codepages = array.array('H', ctypes.string_at(r.value, l.value))
    codepage = tuple(codepages[:2].tolist())

    # Extract information
    ctypes.windll.version.VerQueryValueW(res, (u'\\StringFileInfo\\%04x%04x\\' + info) % codepage, ctypes.byref(r), ctypes.byref(l))
    return ctypes.string_at(r.value, l.value)

# ------------------------------------------------------------------------
# Constants for MessageBox

# WinUser.h
MB_OK                       = 0x00000000L
MB_OKCANCEL                 = 0x00000001L
MB_ABORTRETRYIGNORE         = 0x00000002L
MB_YESNOCANCEL              = 0x00000003L
MB_YESNO                    = 0x00000004L
MB_RETRYCANCEL              = 0x00000005L
MB_CANCELTRYCONTINUE        = 0x00000006L


MB_ICONHAND                 = 0x00000010L
MB_ICONQUESTION             = 0x00000020L
MB_ICONEXCLAMATION          = 0x00000030L
MB_ICONASTERISK             = 0x00000040L

MB_USERICON                 = 0x00000080L
MB_ICONWARNING              = MB_ICONEXCLAMATION
MB_ICONERROR                = MB_ICONHAND

MB_ICONINFORMATION          = MB_ICONASTERISK
MB_ICONSTOP                 = MB_ICONHAND

MB_DEFBUTTON1               = 0x00000000L
MB_DEFBUTTON2               = 0x00000100L
MB_DEFBUTTON3               = 0x00000200L
MB_DEFBUTTON4               = 0x00000300L

MB_APPLMODAL                = 0x00000000L
MB_SYSTEMMODAL              = 0x00001000L
MB_TASKMODAL                = 0x00002000L
MB_HELP                     = 0x00004000L # Help Button

MB_NOFOCUS                  = 0x00008000L
MB_SETFOREGROUND            = 0x00010000L
MB_DEFAULT_DESKTOP_ONLY     = 0x00020000L

MB_TOPMOST                  = 0x00040000L
MB_RIGHT                    = 0x00080000L
MB_RTLREADING               = 0x00100000L

MB_SERVICE_NOTIFICATION     =      0x00200000L
MB_SERVICE_NOTIFICATION_NT3X=      0x00040000L

MB_TYPEMASK                 = 0x0000000FL
MB_ICONMASK                 = 0x000000F0L
MB_DEFMASK                  = 0x00000F00L
MB_MODEMASK                 = 0x00003000L
MB_MISCMASK                 = 0x0000C000L


# ------------------------------------------------------------------------
def MessageBox(hwnd, text, caption, type):
    return ctypes.windll.user32.MessageBoxW(hwnd, unicode(text), unicode(caption), type)

# ------------------------------------------------------------------------
PM_REMOVE                   = 0x0001
WM_QUIT                     = 0x0012

BYTE = ctypes.c_ubyte
CHAR = ctypes.c_char
DWORD = ctypes.c_ulong
LONG = ctypes.c_long
LPARAM = LONG
LPVOID = ctypes.c_void_p
PVOID = ctypes.c_void_p
UINT = ctypes.c_uint
WCHAR = ctypes.c_wchar
WORD = ctypes.c_ushort
WPARAM = UINT



class POINT(ctypes.Structure):
    _fields_ = [
        # windef.h
        ('x', LONG),
        ('y', LONG),
    ]

assert ctypes.sizeof(POINT) == 8, ctypes.sizeof(POINT)
assert ctypes.alignment(POINT) == 4, ctypes.alignment(POINT)

class MSG(ctypes.Structure):
    _fields_ = [
        # winuser.h
        ('hwnd', HWND),
        ('message', UINT),
        ('wParam', WPARAM),
        ('lParam', LPARAM),
        ('time', DWORD),
        ('pt', POINT),
]





def PumpWindowsMessages():
    msg = MSG()
    while ctypes.windll.user32.PeekMessageW(ctypes.byref(msg), HWND(), 0, 0, PM_REMOVE):
        if msg.message == WM_QUIT:
            return False
        ##print ">>> GOT MSG", msg.message, msg.wParam, msg.lParam, msg.time, (msg.pt.x, msg.pt.y)
        ctypes.windll.user32.TranslateMessage(ctypes.byref(msg))
        ctypes.windll.user32.DispatchMessageW(ctypes.byref(msg))
    return True


# ------------------------------------------------------------------------
CF_UNICODETEXT              = 13

def GetClipboardData():
    if not ctypes.windll.user32.OpenClipboard(HWND()):
        raise ctypes.WinError()

    try:
        if not ctypes.windll.user32.IsClipboardFormatAvailable(CF_UNICODETEXT):
            return

        GetClipboardData = ctypes.windll.user32.GetClipboardData
        GetClipboardData.restype = HGLOBAL
        data = GetClipboardData(CF_UNICODETEXT)
        if not data:
            raise ctypes.WinError()

        GlobalLock = ctypes.windll.kernel32.GlobalLock
        GlobalLock.restype = ctypes.c_void_p
        ld = GlobalLock(data)
        if not ld:
            raise ctypes.WinError()

        try:
            return ctypes.c_wchar_p(ld).value
        finally:
            ctypes.windll.kernel32.GlobalUnlock(data) # Ignoring the return value intentionally here

    finally:
        if not ctypes.windll.user32.CloseClipboard():
            raise ctypes.WinError()

# ------------------------------------------------------------------------
def DebugBreak():
    ctypes.windll.kernel32.DebugBreak()

def OutputDebugString(s):
    try:
        ctypes.windll.kernel32.OutputDebugStringW(unicode(s))
    except Exception:
        ctypes.windll.kernel32.OutputDebugStringA(str(s))

# ------------------------------------------------------------------------
def QueryPerformanceFrequency():
    freq = ctypes.c_longlong()
    if not ctypes.windll.kernel32.QueryPerformanceFrequency(ctypes.byref(freq)):
        raise ctypes.WinError()
    return freq.value


# ------------------------------------------------------------------------
def QueryPerformanceCounter():
    counter = ctypes.c_longlong()
    if not ctypes.windll.kernel32.QueryPerformanceCounter(ctypes.byref(counter)):
        raise ctypes.WinError()
    return counter.value


# ------------------------------------------------------------------------
def Sleep(ms):
    ctypes.windll.kernel32.Sleep(ms)


# ------------------------------------------------------------------------
def SetConsoleTitle(title):
    if not ctypes.windll.kernel32.SetConsoleTitleW(unicode(title)):
        raise ctypes.WinError()

# ------------------------------------------------------------------------
def GetCurrentProcessId():
    return ctypes.windll.kernel32.GetCurrentProcessId()


# ------------------------------------------------------------------------
QS_ALLEVENTS                    = 0x04BF
QS_ALLINPUT                     = 0x04FF
QS_RAWINPUT                     = 0x0400
MWMO_ALERTABLE                  = 0x0002

class Waitables(object):
    """Utility class to manage waitable objects. It has a list of waitable objects with
    optional callback function associated. Whenever an object is signaled, the callback
    function is called.
    """

    # ------------------------------------------------------------------------
    def __init__(self):

        # Key is handle, value is callback function.
        self.waitables = weakref.WeakValueDictionary()


    # ------------------------------------------------------------------------
    def InsertHandle(self, handle, callback):
        """InsertHandle(handle, callback) -> handle
        Insert a handle to a waitable object.

        If 'handle' is None, a new event object will be created.

        The 'callback' is a class instance with the following function defined:
        def OnObjectSignaled(self, handle, abandoned)

        If 'abandoned' is False, the 'handle' is signaled.

        A weak reference to the callback is held, so if the  callback function is destroyed,
        the handle is removed from the waitables list as well.
        """
        if handle is None:
            CreateEventW = ctypes.windll.kernel32.CreateEventW
            CreateEventW.restype = HANDLE
            handle = CreateEventW(None, BOOL(), BOOL(), None)
            if not handle:
                raise ctypes.WinError()

        self.waitables[handle] = callback
        return handle


    # ------------------------------------------------------------------------
    def RemoveHandle(self, handle, close = False):
        """Remove handle from waitables.
        If 'close' is true, the handle is closed using Win32's CloseHandle().
        """
        del self.waitables[handle]
        if close:
            if not ctypes.windll.kernel32.CloseHandle(handle):
                raise ctypes.WinError()


    # ------------------------------------------------------------------------
    def Wait(self, milliseconds = 1000):
        """Calls MsgWaitForMultipleObjectsEx for all objects in the list and calls the
        appropriate callback function for all signaled objects.

        The return value is the same as from the MsgWait call.

        In case of signaled or abandoned object, the appropriate callback function
        will be called and MsgWait called again.

        Please note that exceptions from callback functions are delegated upwards.
        """

        # Turn handle list into C handle array
        handles, callbacks = self.waitables.keys(), self.waitables.values()
        HandleArray = ctypes.c_void_p * len(handles)
        handles = HandleArray(*handles)
        ret = ctypes.windll.user32.MsgWaitForMultipleObjectsEx(
            len(handles), handles, milliseconds, QS_ALLINPUT, MWMO_ALERTABLE)


        if WAIT_OBJECT_0 <= ret <= WAIT_OBJECT_0 + len(handles) - 1:
            # The return value minus WAIT_OBJECT_0 indicates the pHandles array index of the
            #object that caused the function to return.
            idx = ret - WAIT_OBJECT_0
            if handles[idx] in self.waitables:
                callbacks[idx].OnObjectSignaled(handles[idx], False)
            return self.Wait(0) # Continue to process pending signals

        elif ret == WAIT_OBJECT_0 + len(handles):
            # New input of the type specified in the dwWakeMask parameter is available in the
            # thread's input queue.
            return ret

        elif WAIT_ABANDONED_0 <= ret <= WAIT_ABANDONED_0 + len(handles) - 1:
            # The return value minus WAIT_ABANDONED_0 indicates the pHandles array index of an
            # abandoned mutex object that caused the function to return.
            idx = ret - WAIT_OBJECT_0
            if handles[idx] in self.waitables:
                callbacks[idx].OnObjectSignaled(handles[idx], True)
            return self.Wait(0) # Continue to process pending signals

        elif ret == WAIT_IO_COMPLETION:
            # The wait was ended by one or more user-mode asynchronous procedure calls (APC)
            # queued to the thread.
            return ret

        elif ret == WAIT_TIMEOUT:
            # The time-out interval elapsed, but the conditions specified by the dwFlags and
            # dwWakeMask parameters were not met.
            return ret

        elif ret == WAIT_FAILED:
            raise ctypes.WinError()
        else:
            raise RuntimeError("Wait: Unknown return value from MsgWaitForMultipleObjectsEx:", ret)


class Clip(object):
    def __str__(self):
        try:
            return GetClipboardData()
        except Exception:
            return "Clip object with broken data"
    def __repr__(self):
        try:
            return GetClipboardData()
        except Exception:
            return "Clip object with broken data"

import __builtin__
__builtin__.clip = Clip()


'''

from ctypes.wintypes import *
MAX_ADAPTER_NAME = 128


class IP_ADAPTER_INDEX_MAP(ctypes.Structure):
    _fields_ = [
        ('Index', ULONG),
        ('Name', WCHAR * MAX_ADAPTER_NAME),
]

class IP_INTERFACE_INFO(ctypes.Structure):
    _fields_ = [
        ('NumAdapters', LONG),
        ('Adapter', IP_ADAPTER_INDEX_MAP * 50)
        ]

def GetInterfaceInfo():
    buff = IP_INTERFACE_INFO()
    size = ctypes.c_long(ctypes.sizeof(IP_INTERFACE_INFO))
    print "sizeof buff is", size.value
    ctypes.windll.Iphlpapi.GetInterfaceInfo(ctypes.byref(buff), ctypes.byref(size))
    print "real size", size.value
    return buff


AF_INET        = 2
AF_INET6       = 23
AF_UNSPEC      = 0

GAA_FLAG_SKIP_UNICAST       = 0x0001
GAA_FLAG_SKIP_ANYCAST       = 0x0002
GAA_FLAG_SKIP_MULTICAST     = 0x0004
GAA_FLAG_SKIP_DNS_SERVER    = 0x0008
GAA_FLAG_INCLUDE_PREFIX     = 0x0010
GAA_FLAG_SKIP_FRIENDLY_NAME = 0x0020

MAX_ADAPTER_ADDRESS_LENGTH     = 8


class _unnamed1(ctypes.Structure):
    _fields_ = [
        ('Length', ULONG),
        ('IfIndex', ULONG),
    ]

class _unnamed2(ctypes.Union):
    _fields_ = [
        ('Alignment', ULARGE_INTEGER),
        ('_unnamed1', _unnamed1),
    ]

class IP_ADAPTER_ADDRESSES(ctypes.Structure):
    pass

IP_ADAPTER_ADDRESSES._fiels_ = [
    ('_unnamed2', _unnamed2),
    ('Next', POINTER(IP_ADAPTER_ADDRESSES)),
    ('AdapterName', LPCSTR),

    ]


typedef struct _IP_ADAPTER_ADDRESSES {
    union {
        ULONGLONG Alignment;
        struct {
            ULONG Length;
            DWORD IfIndex;
        };
    };
    struct _IP_ADAPTER_ADDRESSES *Next;
    PCHAR AdapterName;
    PIP_ADAPTER_UNICAST_ADDRESS FirstUnicastAddress;
    PIP_ADAPTER_ANYCAST_ADDRESS FirstAnycastAddress;
    PIP_ADAPTER_MULTICAST_ADDRESS FirstMulticastAddress;
    PIP_ADAPTER_DNS_SERVER_ADDRESS FirstDnsServerAddress;
    PWCHAR DnsSuffix;
    PWCHAR Description;
    PWCHAR FriendlyName;
    BYTE PhysicalAddress[MAX_ADAPTER_ADDRESS_LENGTH];
    DWORD PhysicalAddressLength;
    DWORD Flags;
    DWORD Mtu;
    DWORD IfType;
    IF_OPER_STATUS OperStatus;
    DWORD Ipv6IfIndex;
    DWORD ZoneIndices[16];
    PIP_ADAPTER_PREFIX FirstPrefix;
} IP_ADAPTER_ADDRESSES, *PIP_ADAPTER_ADDRESSES;



def GetAdaptersAddresses(family = AF_UNSPEC, flag = 0):
    addresses = IP_ADAPTER_ADDRESSES()
    size = ctypes.c_long(ctypes.sizeof(IP_ADAPTER_ADDRESSES))
    ret = ctypes.windll.Iphlpapi.GetAdaptersAddresses(family, flag, 0, ctypes.byref(addresses), ctypes.byref(size))
    return addresses



class MIB_IPADDRTABLE(ctypes.Structure):
    _fields_ = [
        ('dwNumEntries', DWORD),
        ('table', MIB_IPADDRROW * 1),
        ]

'''
